package com.lx.spring.mvc.web;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class MyDispatchServlet extends HttpServlet {

    AnnotationConfigApplicationContext context;

    public MyDispatchServlet(AnnotationConfigApplicationContext context) {
        this.context = context;
    }

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("req.getServletPath() = " + req.getServletPath());

        //根据请求地址从IOC容器中查找控制器方法
        Handler handler = searchHandlerMapping(req.getServletPath());
        System.out.println("handler = " + handler);

        if(handler == null) {
            // 判断是否为静态资源
            if (isStaticResource(req)) {
                //  静态资源
                processStaticResource(req, resp);
            }else {
                // TODO 404
                staticResource404(resp);
            }
        }else{
            // 执行控制器方法
            Object ret = executeHandler(handler, req, resp);
            // 处理控制器方法返回结果 json, 页面跳转
            boolean isRestController = handler.controller.getClass().getAnnotation(RestController.class) != null;
            boolean isResponseBody = handler.getMethod().getAnnotation(ResponseBody.class) != null;
            if(isResponseBody  || isRestController && ret instanceof ModelAndView == false){
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(ret);
                resp.setContentType("application/json;charset=utf-8");
                resp.getWriter().write(json);
            }else{
                // 视图: String -> 视图名      modelAndViwe.getViweName()
                String viweName = null;
                if(ret instanceof String){
                    viweName = (String) ret;
                }else if(ret instanceof ModelAndView){
                    viweName = ((ModelAndView) ret).getViewName();
                }
                // 跳转页面方式: 请求转发 响应重定向
                boolean isForward = false;
                if(viweName.startsWith("forward:")){
                    // 请求转发
                    isForward = true;
                    viweName = viweName.substring("forward:".length());
                }else if(viweName.startsWith("redirect:")){
                    // 响应重定向
                    viweName = viweName.substring("redirect:".length());
                }else {
                    // 请求转发
                    isForward = true;
                }

                if(isForward){
                    //请求转发
                    RequestDispatcher requestDispatcher = req.getRequestDispatcher(viweName);
                    requestDispatcher.forward(req, resp);
                }else{
                    //响应重定向
                    resp.sendRedirect(viweName);
                }
            }
        }


    }

    private Object executeHandler(Handler handler,
                                  HttpServletRequest req,
                                  HttpServletResponse resp) {
        // 获取控制器方法的参数列表
        Parameter[] parameters = handler.method.getParameters();
        // 获取参数值
        List<Object> paramValues = new ArrayList<>();

        LocalVariableTableParameterNameDiscoverer lv = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = lv.getParameterNames(handler.method);

        int i = 0;
        for (Parameter parameter : parameters) {
//            String name = parameter.getName();
            String name = parameterNames[i++];
            String sValue = req.getParameter(name);
            // 参数类型
            Class<?> type = parameter.getType();
            Object oValue = sValue;
            if(type.isAssignableFrom(Integer.class) || type.isAssignableFrom(int.class)) {
                oValue = Integer.parseInt(sValue);
            }else if(type.isAssignableFrom(Boolean.class) || type.isAssignableFrom(boolean.class)) {
                oValue = Boolean.valueOf(sValue);
            }else if(type.isAssignableFrom(HttpServletRequest.class)) {
                // 字符串不用处理
                oValue = req;
            }else if(type.isAssignableFrom(HttpServletResponse.class)) {
                // 字符串不用处理
                oValue = resp;
            }else if(type.isAssignableFrom(HttpSession.class)) {
                // 字符串不用处理
                oValue = req.getSession();
            }else if(type.isAssignableFrom(ModelAndView.class)) {
                // 字符串不用处理
                oValue = new ModelAndView();
                //其他类型处理完成之后, 就剩下实体对象
            }else{
                // 实体对象 User
                // 使用反射将请求参数映射到对象属性中
                try{
                    Object o = parameter.getType().newInstance();
                    oValue = o;
                    for(Field field : o.getClass().getDeclaredFields()){
                        String fieldName = field.getName();
                        String fieldSvalue = req.getParameter(fieldName);
                        if(fieldSvalue == null){
                            continue;
                        }
                        Object fieldOvalue = fieldSvalue;
                        if(field.getType().isAssignableFrom(int.class)){
                            fieldOvalue = Integer.valueOf(fieldSvalue);
                        }
                        field.setAccessible(true);
                        field.set(o, fieldOvalue);
                    }

                }catch (Exception e){
                    throw new RuntimeException(e);
                }
            }
            paramValues.add(oValue);
        }

        //执行控制器方法
        handler.method.setAccessible(true);
        try {
            Object ret = handler.method.invoke(handler.controller, paramValues.toArray());
            //返回结果
            return ret;
        } catch (Exception e) {
            throw new RuntimeException("控制器方法错误", e);
        }

    }

    private void staticResource404(HttpServletResponse resp) {
        try{
            ServletOutputStream out = resp.getOutputStream();
            String s = "<h1>" + "not find 404" + "</h1>";
            byte[] bytes = s.getBytes("utf-8");
            out.write(bytes);
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    private void processStaticResource(HttpServletRequest req, HttpServletResponse resp) {
        // 将文件发送给浏览器
        String webPath = req.getServletPath();
        String filePath = req.getServletContext().getRealPath(webPath);
        try(FileInputStream fis = new FileInputStream(new File(filePath))){
            //ServletOutputStream 服务器的输出流, 关闭由服务器绝对，无需手动关闭
            ServletOutputStream out = resp.getOutputStream();
            byte[] bytes = new byte[1024];
            int count;
            while((count = fis.read(bytes)) > -1) {
                out.write(bytes, 0, count);
            }

        }catch (IOException e){
            throw new RuntimeException("读取文件资源错误",e);
        }
    }




    private boolean isStaticResource(HttpServletRequest req) {
        // webpath = /index.html;
        String webPath = req.getServletPath();
        // D:\git store\ycdemo-3\spring\src\main\webapp\index.html
        // getRealPath 将资源的web路径转换为磁盘路径
        String filePath = req.getServletContext().getRealPath(webPath);
        //判断文件是否存在
        return new File(filePath).exists();
    }

    private Handler searchHandlerMapping(String path) {
        Map<String, Object> restController = context.getBeansWithAnnotation(RestController.class);
        Map<String, Object> controllers = context.getBeansWithAnnotation(Controller.class);
        controllers.putAll(restController);

        // 搜索控制器方法 + 控制器对象
        for (Object controller : controllers.values()) {
            String prefixPath = null;
            RequestMapping requestMapping = controller.getClass().getAnnotation(RequestMapping.class);
            if (requestMapping != null) {
                prefixPath = requestMapping.value()[0];
            }
            for (Method method : controller.getClass().getDeclaredMethods()) {
                String suffixPath;
                RequestMapping requestMapping1 = method.getAnnotation(RequestMapping.class);
                PostMapping postMapping = method.getAnnotation(PostMapping.class);
                GetMapping getMapping = method.getAnnotation(GetMapping.class);
                suffixPath = getMapping != null && getMapping.value().length > 0
                        ? getMapping.value()[0] : null;
                if(suffixPath != null) {
                    String handlerPath = "/" + suffixPath;
                    if (prefixPath != null){
                        handlerPath = "/" + prefixPath + handlerPath;
                    }
                    if(handlerPath.equals(path)){
                        Handler handler = new Handler(method, controller);
                        return handler;
                    }
                }
            }

        }
        return null;
    }

    @AllArgsConstructor
    @Data
    class Handler{
        //控制器方法
        Method method;
        //控制器对象
        Object controller;
    }
}
